{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Day 1 Solution - Code Explainer Implementation\n",
        "This is my solution to the Day 1 assignment. I've implemented a code explanation tool using Llama 3.2 via Ollama.\n",
        "\n",
        "## Features Implemented:\n",
        "- Local LLM integration using Ollama (Llama 3.2)\n",
        "- OpenAI API-compatible interface\n",
        "- Code analysis with line-by-line explanations\n",
        "- Beginner-friendly explanations with highlighted concepts\n",
        "- Markdown display formatting for rich output\n",
        "- Environment variable configuration with .env support\n",
        "- Multiple code example demonstrations (recursion, loops, list comprehensions, classes)\n",
        "\n",
        "## Technologies Used:\n",
        "- **Ollama**: Local LLM runtime (no API costs)\n",
        "- **Llama 3.2**: Meta's language model\n",
        "- **OpenAI Python SDK**: API interface\n",
        "- **IPython**: Rich markdown display\n",
        "- **python-dotenv**: Environment management\n",
        "\n",
        "## Why Ollama?\n",
        "I chose to use Ollama instead of the OpenAI API to:\n",
        "- Avoid API key costs and usage limits\n",
        "- Run the model completely offline\n",
        "- Gain experience with local LLM deployment\n",
        "- Maintain data privacy\n",
        "\n",
        "## Setup Requirements:\n",
        "1. Ollama installed locally\n",
        "2. Llama 3.2 model pulled\n",
        "3. Python packages: openai, python-dotenv, ipython"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "# imports\n",
        "import os\n",
        "from openai import OpenAI\n",
        "from dotenv import load_dotenv\n",
        "from IPython.display import Markdown, display"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "True"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Load environment variables (optional for Ollama, but good practice)\n",
        "load_dotenv()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Connect to Ollama\n",
        "client = OpenAI(\n",
        "    base_url='http://localhost:11434/v1',\n",
        "    api_key='ollama',  # Not required for Ollama but mandatory parameter\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 1: Create your prompts\n",
        "system_prompt = \"\"\"You are an experienced programming instructor.\n",
        "You analyze and explain code snippets line by line.\n",
        "Format your explanations as follows:\n",
        "\n",
        "1. Summarize the overall purpose of the code\n",
        "2. Explain each important line or block\n",
        "3. Highlight important concepts used\n",
        "4. Use language that beginners can understand\n",
        "\n",
        "Provide clear and educational explanations.\"\"\"\n",
        "\n",
        "user_prompt = \"\"\"\n",
        "Explain the following Python code:\n",
        "\n",
        "def fibonacci(n):\n",
        "    if n <= 1:\n",
        "        return n\n",
        "    else:\n",
        "        return fibonacci(n-1) + fibonacci(n-2)\n",
        "\n",
        "result = fibonacci(5)\n",
        "print(result)\n",
        "\"\"\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Make the messages list\n",
        "messages = [\n",
        "    {\"role\": \"system\", \"content\": system_prompt},\n",
        "    {\"role\": \"user\", \"content\": user_prompt}\n",
        "]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Call Ollama (using OpenAI API format)\n",
        "response = client.chat.completions.create(\n",
        "    model=\"llama3.2\",\n",
        "    messages=messages,\n",
        "    temperature=0.3\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "Let's break down this Python code step by step.\n",
              "\n",
              "**Overall Purpose of the Code**\n",
              "The overall purpose of this code is to calculate and print the 5th number in the Fibonacci sequence. The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones, usually starting with 0 and 1.\n",
              "\n",
              "**Line-by-Line Explanation**\n",
              "\n",
              "```python\n",
              "def fibonacci(n):\n",
              "```\n",
              "\n",
              "This line defines a function named `fibonacci` that takes one argument `n`.\n",
              "\n",
              "```python\n",
              "if n <= 1:\n",
              "    return n\n",
              "```\n",
              "\n",
              "Inside the function, this block checks if the input number `n` is less than or equal to 1. If it is, the function returns the value of `n`. This makes sense because the first two numbers in the Fibonacci sequence are 0 and 1.\n",
              "\n",
              "```python\n",
              "else:\n",
              "    return fibonacci(n-1) + fibonacci(n-2)\n",
              "```\n",
              "\n",
              "If `n` is greater than 1, this block calls itself recursively twice. It calculates the (n-1)th number by calling `fibonacci(n-1)` and the (n-2)th number by calling `fibonacci(n-2)`. Then, it returns the sum of these two numbers.\n",
              "\n",
              "```python\n",
              "result = fibonacci(5)\n",
              "```\n",
              "\n",
              "This line calls the `fibonacci` function with an argument of 5 and stores the result in a variable named `result`.\n",
              "\n",
              "```python\n",
              "print(result)\n",
              "```\n",
              "\n",
              "Finally, this line prints the value of `result`, which is the 5th number in the Fibonacci sequence.\n",
              "\n",
              "**Important Concepts Used**\n",
              "\n",
              "* **Recursion**: This code uses recursion to solve the problem. Recursion is a technique where a function calls itself repeatedly until it reaches a base case.\n",
              "* **Base Case**: In this code, the base case is when `n` is less than or equal to 1. When this condition is met, the function returns immediately without calling itself again.\n",
              "* **Memoization**: While not explicitly shown in this code, recursive functions like this one can benefit from memoization, which is a technique where the results of expensive function calls are cached so that they can be reused instead of recalculated.\n",
              "\n",
              "**Tips for Beginners**\n",
              "\n",
              "* When working with recursion, make sure you understand the base case and how it will terminate the recursion.\n",
              "* Use descriptive variable names to help you (and others) understand what your code is doing.\n",
              "* Don't be afraid to try breaking down complex problems into smaller sub-problems – this can often lead to elegant solutions!"
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Step 4: Display the result (using IPython for nice formatting)\n",
        "display(Markdown(response.choices[0].message.content))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "✅ Interactive function ready! Use: explain_code_interactive('your code here')\n"
          ]
        }
      ],
      "source": [
        "# ==============================================\n",
        "# INTERACTIVE CODE EXPLAINER FUNCTION\n",
        "# ==============================================\n",
        "def explain_code_interactive(code_snippet):\n",
        "    \"\"\"\n",
        "    Explain any code snippet interactively\n",
        "    Just call this function with your code as a string\n",
        "    \"\"\"\n",
        "    messages = [\n",
        "        {\"role\": \"system\", \"content\": system_prompt},\n",
        "        {\"role\": \"user\", \"content\": f\"Explain the following code:\\n\\n{code_snippet}\"}\n",
        "    ]\n",
        "    \n",
        "    response = client.chat.completions.create(\n",
        "        model=\"llama3.2\",\n",
        "        messages=messages,\n",
        "        temperature=0.3\n",
        "    )\n",
        "    \n",
        "    display(Markdown(response.choices[0].message.content))\n",
        "\n",
        "print(\"✅ Interactive function ready! Use: explain_code_interactive('your code here')\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "📌 Example 1: Simple Loop\n",
            "\n"
          ]
        },
        {
          "data": {
            "text/markdown": [
              "Let's break down this code step by step.\n",
              "\n",
              "**Overall Purpose:**\n",
              "The purpose of this code is to print the multiples of a number from 0 to 4, multiplied by 2.\n",
              "\n",
              "**Line-by-Line Explanation:**\n",
              "\n",
              "1. `for i in range(5):`\n",
              "   - This line starts a loop that will run 5 times.\n",
              "   - The variable `i` represents the current iteration (or position) in the loop.\n",
              "   - `range(5)` generates numbers from 0 to 4.\n",
              "\n",
              "2. `print(i * 2)`\n",
              "   - Inside the loop, this line prints the value of `i` multiplied by 2.\n",
              "   - So, for each iteration of the loop, it will print a multiple of 2 (e.g., 0*2 = 0, 1*2 = 2, etc.).\n",
              "\n",
              "**Important Concepts:**\n",
              "\n",
              "- **Loops:** This code uses a `for` loop to repeat a set of instructions. Loops are useful when you need to perform the same task multiple times.\n",
              "- **Range Function:** The `range()` function generates numbers from a specified start value up to, but not including, a stop value. In this case, it starts at 0 and stops before reaching 5.\n",
              "- **Multiplication Operator:** The `*` operator is used for multiplication in Python.\n",
              "\n",
              "**Example Output:**\n",
              "The output of this code will be:\n",
              "```\n",
              "0\n",
              "2\n",
              "4\n",
              "6\n",
              "8\n",
              "```\n",
              "\n",
              "This code demonstrates a basic concept in programming, which is to use loops to repeat tasks. It's a simple yet useful example that can help beginners understand how loops work in Python."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Example 1: Simple Loop\n",
        "print(\"📌 Example 1: Simple Loop\\n\")\n",
        "\n",
        "explain_code_interactive(\"\"\"\n",
        "for i in range(5):\n",
        "    print(i * 2)\n",
        "\"\"\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "📌 Example 2: List Comprehension\n",
            "\n"
          ]
        },
        {
          "data": {
            "text/markdown": [
              "Let's break down this code step by step.\n",
              "\n",
              "**Overall Purpose of the Code**\n",
              "The purpose of this code is to create a new list called `squares` that contains the squares of even numbers from the original list `numbers`.\n",
              "\n",
              "**Line-by-Line Explanation**\n",
              "\n",
              "1. `numbers = [1, 2, 3, 4, 5]`\n",
              "   This line creates a new list called `numbers` and assigns it a value containing five integers: 1, 2, 3, 4, and 5.\n",
              "\n",
              "2. `squares = [x**2 for x in numbers if x % 2 == 0]`\n",
              "   This line is using a feature of Python called **list comprehension**. It's creating a new list called `squares` by performing an operation on each element of the `numbers` list.\n",
              "   - `for x in numbers`: This part loops over each number in the `numbers` list and assigns it to the variable `x`.\n",
              "   - `if x % 2 == 0`: This is a condition that filters out only the even numbers from the list. The `%` operator calculates the remainder of dividing `x` by 2, and if this remainder is 0 (i.e., `x` is divisible by 2), then the number is even.\n",
              "   - `x**2`: This part squares each even number found in the previous step.\n",
              "\n",
              "3. `print(squares)`\n",
              "   Finally, this line prints out the resulting list of squares to the console.\n",
              "\n",
              "**Important Concepts Used**\n",
              "\n",
              "* **List Comprehension**: A concise way to create a new list by performing an operation on each element of an existing list.\n",
              "* **Conditional Statement (`if`)**: Used to filter or conditionally apply operations to elements in a list.\n",
              "* **Modulus Operator (`%`)**: Calculates the remainder of dividing one number by another. In this case, it's used to check if a number is even.\n",
              "\n",
              "**Example Output**\n",
              "When you run this code, the output will be:\n",
              "```\n",
              "[4, 16]\n",
              "```\n",
              "This is because only the even numbers (2 and 4) in the original list `numbers` are squared and included in the resulting list `squares`."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Example 2: List Comprehension\n",
        "print(\"📌 Example 2: List Comprehension\\n\")\n",
        "\n",
        "explain_code_interactive(\"\"\"\n",
        "numbers = [1, 2, 3, 4, 5]\n",
        "squares = [x**2 for x in numbers if x % 2 == 0]\n",
        "print(squares)\n",
        "\"\"\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "📌 Example 3: Class Definition\n",
            "\n"
          ]
        },
        {
          "data": {
            "text/markdown": [
              "**Overall Purpose of the Code**\n",
              "\n",
              "This code defines a simple class called `Dog` that represents an animal with a name and age. It provides methods for the dog to bark and display its information.\n",
              "\n",
              "**Line-by-Line Explanation**\n",
              "\n",
              "1. `class Dog:` This line begins the definition of a new class called `Dog`.\n",
              "\n",
              "2. `def __init__(self, name, age):` This line defines a special method called `__init__`, which is used to initialize (or create) an object when it's first created. The `(name, age)` part means that when we create a new `Dog` object, we need to provide two parameters: the dog's name and its age.\n",
              "\n",
              "3. `self.name = name` and `self.age = age:` These lines assign the provided `name` and `age` values to instance variables (also known as attributes) of the class called `self`. The `self` keyword is a reference to the current instance of the class and is used to access variables that belong to the class.\n",
              "\n",
              "4. `def bark(self):` This line defines another method called `bark`, which returns a string message indicating what the dog says when it barks.\n",
              "\n",
              "5. `return f\"{self.name} says Woof!\"` This line uses an f-string (a feature introduced in Python 3.6) to create a formatted string that includes the dog's name and the message \"Woof!\". The `f` before the string indicates that it's an f-string, which allows us to embed expressions inside the string.\n",
              "\n",
              "6. `def get_info(self):` This line defines another method called `get_info`, which returns a string message indicating what information is available about the dog.\n",
              "\n",
              "7. `return f\"{self.name} is {self.age} years old\"` Similar to the previous line, this uses an f-string to create a formatted string that includes the dog's name and age.\n",
              "\n",
              "8. `my_dog = Dog(\"Buddy\", 3)` This line creates a new object called `my_dog` by providing its name as `\"Buddy\"` and its age as `3`.\n",
              "\n",
              "9. `print(my_dog.bark())` This line calls the `bark()` method on `my_dog`, which prints out the message \"Buddy says Woof!\".\n",
              "\n",
              "10. `print(my_dog.get_info())` This line calls the `get_info()` method on `my_dog`, which prints out the message \"Buddy is 3 years old\".\n",
              "\n",
              "**Important Concepts Used**\n",
              "\n",
              "* Classes and objects: The code defines a class called `Dog` that represents an animal, and creates an object called `my_dog` based on this class.\n",
              "* Methods: The code defines methods (functions) within the class to perform actions like barking and getting information about the dog.\n",
              "* Instance variables: The code uses instance variables (`self.name` and `self.age`) to store data that belongs to each individual dog object.\n",
              "* F-strings: The code uses f-strings to create formatted strings with embedded expressions.\n",
              "\n",
              "**Beginner-Friendly Explanation**\n",
              "\n",
              "Think of this code like a simple restaurant menu. The class `Dog` is like the menu itself, which defines what's available (the methods and attributes). When we create an object called `my_dog`, it's like ordering food at that restaurant – we provide some information about ourselves (`name` and `age`) to help the system understand what we want.\n",
              "\n",
              "The `bark()` method is like a special request to the chef, which returns a prepared message with our name included. The `get_info()` method is like asking for the menu itself, which returns all the available information about the dog."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Example 3: Object-Oriented Programming\n",
        "print(\"📌 Example 3: Class Definition\\n\")\n",
        "\n",
        "explain_code_interactive(\"\"\"\n",
        "class Dog:\n",
        "    def __init__(self, name, age):\n",
        "        self.name = name\n",
        "        self.age = age\n",
        "    \n",
        "    def bark(self):\n",
        "        return f\"{self.name} says Woof!\"\n",
        "    \n",
        "    def get_info(self):\n",
        "        return f\"{self.name} is {self.age} years old\"\n",
        "\n",
        "my_dog = Dog(\"Buddy\", 3)\n",
        "print(my_dog.bark())\n",
        "print(my_dog.get_info())\n",
        "\"\"\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "📌 Example 4: Python Decorators\n",
            "\n"
          ]
        },
        {
          "data": {
            "text/markdown": [
              "**Overall Purpose of the Code**\n",
              "\n",
              "The purpose of this code is to create a decorator that measures the execution time of a function. The decorator, named `timer_decorator`, wraps around another function (`slow_function`) and prints out how long it took for the wrapped function to execute.\n",
              "\n",
              "**Line-by-Line Explanation**\n",
              "\n",
              "1. `def timer_decorator(func):`\n",
              "   - This line defines a new function called `timer_decorator`. It takes one argument, `func`, which is the function that we want to decorate (wrap around) with our decorator.\n",
              "   - The `timer_decorator` function returns another function, which we'll call `wrapper`.\n",
              "\n",
              "2. `def wrapper(*args, **kwargs):`\n",
              "   - This line defines a new function called `wrapper`. It takes in any number of arguments (`*args`) and keyword arguments (`**kwargs`), just like the original function that it's wrapping around.\n",
              "   - The `wrapper` function is where we'll put our timer logic.\n",
              "\n",
              "3. `import time`\n",
              "   - This line imports the built-in Python module called `time`. We're going to use this module to measure how long it takes for the wrapped function to execute.\n",
              "\n",
              "4. `start = time.time()`\n",
              "   - This line records the current time in seconds since the epoch (January 1, 1970) and stores it in a variable called `start`.\n",
              "   - Think of this as taking a snapshot of the current time before we start executing our wrapped function.\n",
              "\n",
              "5. `result = func(*args, **kwargs)`\n",
              "   - This line calls the original function that we're wrapping around (`func`) with any arguments that were passed to us (`*args` and `**kwargs`).\n",
              "   - The result of this call is stored in a variable called `result`.\n",
              "\n",
              "6. `end = time.time()`\n",
              "   - This line records the current time again, just like we did earlier.\n",
              "   - Think of this as taking another snapshot of the current time after our wrapped function has finished executing.\n",
              "\n",
              "7. `print(f\"Function took {end - start:.4f} seconds\")`\n",
              "   - This line calculates how long it took for our wrapped function to execute by subtracting the `start` time from the `end` time.\n",
              "   - It then prints out this execution time with four decimal places using an f-string.\n",
              "\n",
              "8. `return result`\n",
              "   - This line returns the result of the original function call (`result`) to whoever called us.\n",
              "\n",
              "9. `@timer_decorator`\n",
              "   - This line is a Python decorator syntax. It's equivalent to writing `slow_function = timer_decorator(slow_function)`.\n",
              "   - When we use this syntax, we're telling Python to replace our `slow_function` with the `wrapper` function that we defined inside `timer_decorator`.\n",
              "\n",
              "10. `def slow_function():`\n",
              "    - This line defines a new function called `slow_function`. It's just a placeholder for now.\n",
              "\n",
              "11. `import time`\n",
              "    - This line imports the built-in Python module called `time`.\n",
              "    - We're going to use this module to simulate some work being done by our wrapped function.\n",
              "\n",
              "12. `time.sleep(1)`\n",
              "    - This line causes our wrapped function to sleep for one second.\n",
              "    - Think of this as simulating some work that takes a long time to do.\n",
              "\n",
              "13. `return \"Done!\"`\n",
              "    - This line returns the string `\"Done!\"` to whoever called us.\n",
              "    - It's just a placeholder; we could replace it with any other value or logic.\n",
              "\n",
              "14. `slow_function()`\n",
              "    - This line calls our wrapped function (`slow_function`) and prints out its execution time.\n",
              "\n",
              "**Important Concepts Used**\n",
              "\n",
              "*   **Decorators**: A way in Python to modify the behavior of an existing function without changing its implementation.\n",
              "*   **Function Wrapping**: The process of wrapping one function around another, allowing us to add additional functionality or behavior to the original function.\n",
              "*   **Time Module**: A built-in Python module that provides functions for working with time and dates.\n",
              "*   **F-Strings**: A way in Python to format strings using curly brackets `{}` and placeholders (`{variable_name}`)."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Example 4: Decorators (Advanced Concept)\n",
        "print(\"📌 Example 4: Python Decorators\\n\")\n",
        "\n",
        "explain_code_interactive(\"\"\"\n",
        "def timer_decorator(func):\n",
        "    def wrapper(*args, **kwargs):\n",
        "        import time\n",
        "        start = time.time()\n",
        "        result = func(*args, **kwargs)\n",
        "        end = time.time()\n",
        "        print(f\"Function took {end - start:.4f} seconds\")\n",
        "        return result\n",
        "    return wrapper\n",
        "\n",
        "@timer_decorator\n",
        "def slow_function():\n",
        "    import time\n",
        "    time.sleep(1)\n",
        "    return \"Done!\"\n",
        "\n",
        "slow_function()\n",
        "\"\"\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".venv",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
